#include "StdAfx.h"
#include "TimeZone.h"
#include "DoomsdayDate.h"

//--------------------------------------------------------------------------------
TimeZone::TimeZone::StandardRange::StandardRange(void)
{
	memset(this, 0, sizeof(*this));
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::StandardRange::clear(void)
{
	memset(this, 0, sizeof(*this));
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::StandardRange::print(const _tstring& prefix) const
{
	wprintf_s(prefix.c_str()); wprintf_s(L"standard date:\t"); m_start.print(); wprintf_s(L"\n");
	wprintf_s(prefix.c_str()); wprintf_s(L"daylight date:\t"); m_end.print(); wprintf_s(L"\n");
}
//--------------------------------------------------------------------------------
bool TimeZone::TimeZone::StandardRange::inRange(const Time& local_time) const
{
	if (m_start == m_end) // fk u Spielberg!
	{
		return true; // suppose no daylight saving time
	}

	Time start_time, end_time;
	_timeFromRangeBorder(m_start, local_time.wYear, &start_time);
	_timeFromRangeBorder(m_end  , local_time.wYear, &end_time);

	if (m_end < m_start)
	{
		return start_time <= local_time || local_time < end_time;
	}
	else // if (m_end < m_start)
	{
		return start_time <= local_time && local_time < end_time;
	}
}
//--------------------------------------------------------------------------------
/* tests following ranges for hit:
   1:[year start, least border), 2:[least border, most border), 3:[most border, next year start)
*/
int TimeZone::TimeZone::StandardRange::hitWithinYear(const Time& local_time, Time* start_at, Time* expires_at) const
{
	if (m_end < m_start)
	{
		_timeFromRangeBorder(m_end, local_time.wYear, start_at);
		_timeFromRangeBorder(m_start, local_time.wYear, expires_at);
	}
	else
	{
		_timeFromRangeBorder(m_start, local_time.wYear, start_at);
		_timeFromRangeBorder(m_end, local_time.wYear, expires_at);
	}

	if (local_time < *start_at)
	{
		*expires_at = *start_at;
		_yearStart(local_time.wYear, start_at);

		return 1;
	}
	else if (*expires_at <= local_time)
	{
		*start_at = *expires_at;
		_yearStart(local_time.wYear + 1, expires_at);

		return 3;
	}

	return 2;
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::StandardRange::_timeFromRangeBorder(const Time& border, int year, Time* dest)
{
	*dest = border;
	dest->wYear = year;
	dest->wDay = DoomsdayDate(border.wMonth, 1, year).sundayDay(border.wDay);
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::StandardRange::_yearStart(int year, Time* dest)
{
	memset(dest, 0, sizeof(Time));
	dest->wYear = year;
	dest->wMonth = dest->wDay = 1;
}

//--------------------------------------------------------------------------------
TimeZone::TimeZone::TZI::TZI(void)
	: m_bias(0)
	, m_standard_bias(0)
	, m_daylight_bias(0)
{}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::TZI::fill(TIME_ZONE_INFORMATION* dest) const
{
	if (dest)
	{
		memset(dest, 0, sizeof(TIME_ZONE_INFORMATION));
		dest->Bias         = m_bias;
		dest->StandardDate = m_standard_range.m_start;
		dest->StandardBias = m_standard_bias;
		dest->DaylightDate = m_standard_range.m_end;
		dest->DaylightBias = m_daylight_bias;
	}
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::TZI::clear(void)
{
	m_bias = m_standard_bias = m_daylight_bias = 0;
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::TZI::print(const _tstring& prefix) const
{
	wprintf_s(prefix.c_str()); wprintf_s(L"bias:\t%d\n", m_bias);
	wprintf_s(prefix.c_str()); wprintf_s(L"standard bias:\t%d\n", m_standard_bias);
	wprintf_s(prefix.c_str()); wprintf_s(L"daylight bias:\t%d\n", m_daylight_bias);
	m_standard_range.print(prefix);
}

//--------------------------------------------------------------------------------
TimeZone::TimeZone::TimeZone(void)
{}
//--------------------------------------------------------------------------------
TimeZone::TimeZone::~TimeZone(void)
{}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::print(const _tstring& prefix) const
{
	wprintf_s(prefix.c_str()); wprintf_s(L"display name:\t%s\n", m_display_name.c_str());
	wprintf_s(prefix.c_str()); wprintf_s(L"standard name:\t%s\n", m_standard_name.c_str());
	wprintf_s(prefix.c_str()); wprintf_s(L"daylight name:\t%s\n", m_daylight_name.c_str());
	wprintf_s(prefix.c_str()); wprintf_s(L"unique index:\t%i\n", m_unique_index);
	
	_tstring tzi_prefix(prefix);
	tzi_prefix.append(L"\t");

	wprintf_s(prefix.c_str()); wprintf_s(L"Time Zone Information:\n");
	m_tzi.print(tzi_prefix);

	DynamicDST::const_iterator d_iter(m_dsts.begin()), d_end(m_dsts.end());
	for (; d_iter != d_end; ++d_iter)
	{
		wprintf_s(prefix.c_str()); wprintf_s(L"Dynamic Time Zone Information for %04d:\n", d_iter->first);
		d_iter->second.print(tzi_prefix);
	}
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::clear(void)
{
	m_display_name.clear();
	m_standard_name.clear();
	m_daylight_name.clear();
	m_unique_index = 0;
	m_tzi.clear();
	m_dsts.clear();
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::enumZones(void)
{
	HKEY root_key(0);
	
	if (::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", 0, KEY_READ, &root_key)
		!= ERROR_SUCCESS)
	{
		//FormatMessage(...);
		return;
	}

	unsigned int	index(0);
	wchar_t			name[1024];
	unsigned long	name_size(sizeof(name) / sizeof(name[0]));
	FILETIME		last_write;

	while (::RegEnumKeyExW(root_key, index, name, &name_size, 0, 0, 0, &last_write) == ERROR_SUCCESS)
	{
		TimeZone tz;

		wprintf_s(L"\n%s\n", name);

		if (!_loadTimeZone(root_key, name, &tz))
		{
			wprintf_s(L"load failed.\n");
		}

		tz.print(L"\t");

		name_size = sizeof(name) / sizeof(name[0]);
		++index;
	}

	::RegCloseKey(root_key);
}
//--------------------------------------------------------------------------------
bool TimeZone::TimeZone::_loadTimeZone(HKEY root_key, const wchar_t* name, TimeZone* dest)
{
	HKEY tz_key;

	if (::RegOpenKeyExW(root_key, name, 0, KEY_READ, &tz_key) != ERROR_SUCCESS)
	{
		return false;
	}

	unsigned long buf_size(0);

	if (!_loadString(tz_key, L"Display", &dest->m_display_name))
	{
		return false;
	}
	if (!_loadString(tz_key, L"Std", &dest->m_standard_name))
	{
		return false;
	}
	if (!_loadString(tz_key, L"Dlt", &dest->m_daylight_name))
	{
		return false;
	}

	buf_size = sizeof(dest->m_unique_index);
	if (::RegQueryValueExW(tz_key, L"Index", 0, 0, reinterpret_cast<unsigned char*>(&dest->m_unique_index), &buf_size) != ERROR_SUCCESS)
	{
		return false;
	}

	buf_size = sizeof(dest->m_tzi);
	if (::RegQueryValueExW(tz_key, L"TZI", 0, 0, reinterpret_cast<unsigned char*>(&dest->m_tzi), &buf_size) != ERROR_SUCCESS)
	{
		return false;
	}

	_loadDynamicDst(tz_key, &dest->m_dsts);

	::RegCloseKey(tz_key);

	return true;
}
//--------------------------------------------------------------------------------
bool TimeZone::TimeZone::_loadDynamicDst(HKEY tz_key, DynamicDST* dest)
{
	HKEY dst_key;

	if (::RegOpenKeyExW(tz_key, L"Dynamic DST", 0, KEY_READ, &dst_key) != ERROR_SUCCESS)
	{
		return false;
	}

	unsigned long buf_size(0);
	int first_entry(0), last_entry(0);

	buf_size = sizeof(first_entry);
	if (::RegQueryValueExW(dst_key, L"FirstEntry", 0, 0, reinterpret_cast<unsigned char*>(&first_entry), &buf_size) != ERROR_SUCCESS)
	{
		return false;
	}

	buf_size = sizeof(last_entry);
	if (::RegQueryValueExW(dst_key, L"LastEntry", 0, 0, reinterpret_cast<unsigned char*>(&last_entry), &buf_size) != ERROR_SUCCESS)
	{
		return false;
	}

	if (first_entry > last_entry || first_entry < 1900 || last_entry > 3000)
	{
		return false;
	}

	for (int year = first_entry; year <= last_entry; ++year)
	{
		wchar_t name[128];
		swprintf_s(name, sizeof(name) / sizeof(name[0]), L"%d", year);

		TZI tzi;
		buf_size = sizeof(TZI);
		if (::RegQueryValueExW(dst_key, name, 0, 0, reinterpret_cast<unsigned char*>(&tzi), &buf_size) != ERROR_SUCCESS)
		{
			//return false; - not an error
		}
		else
		{
			dest->insert(DynamicDST::value_type(year, tzi));
		}
	}

	::RegCloseKey(dst_key);

	return true;
}
//--------------------------------------------------------------------------------
bool TimeZone::TimeZone::_loadString(HKEY key, const wchar_t* name, _tstring* dest)
{
	unsigned long buf_size(0);

	if (::RegQueryValueExW(key, name, 0, 0, 0, &buf_size) != ERROR_SUCCESS)
	{
		return false;
	}
	dest->resize(buf_size);

	return ::RegQueryValueExW(key, name, 0, 0, reinterpret_cast<unsigned char*>(&(*dest)[0]), &buf_size) == ERROR_SUCCESS;
}
//--------------------------------------------------------------------------------
bool TimeZone::TimeZone::getZone(int index, TimeZone* dest)
{
	HKEY root_key(0);
	
	if (::RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", 0, KEY_READ, &root_key)
		!= ERROR_SUCCESS)
	{
		//FormatMessage(...);
		return false;
	}

	unsigned int	reg_index(0);
	wchar_t			name[1024];
	unsigned long	name_size(sizeof(name) / sizeof(name[0]));
	FILETIME		last_write;

	while (::RegEnumKeyExW(root_key, reg_index, name, &name_size, 0, 0, 0, &last_write) == ERROR_SUCCESS)
	{
		dest->clear();

		if (!_loadTimeZone(root_key, name, dest))
		{
			break;
		}

		if (dest->m_unique_index == index)
		{
			::RegCloseKey(root_key);
			return true;
		}

		name_size = sizeof(name) / sizeof(name[0]);
		++reg_index;
	}

	::RegCloseKey(root_key);

	return false;
}
//--------------------------------------------------------------------------------
const TimeZone::TimeZone::TZI& TimeZone::TimeZone::_tziFor(const Time& local_time) const
{
	std::pair<DynamicDST::const_iterator, DynamicDST::const_iterator> pr = m_dsts.equal_range(local_time.wYear);

	if (pr.first == m_dsts.end()) // current info is actual
	{
		return m_tzi;
	}
	if (pr.first != pr.second) // hit
	{
		return pr.first->second;
	}
	
	// lower bound is greater

	return pr.first == m_dsts.begin() ? pr.first->second : (--pr.first)->second;
}
//--------------------------------------------------------------------------------
int TimeZone::TimeZone::biasMinutes(const Time& local_time, Time* started_at, Time* expired_at) const
{
	const TZI& tzi = _tziFor(local_time);

	if (started_at && expired_at)
	{
		tzi.m_standard_range.hitWithinYear(local_time, started_at, expired_at);
	}

	return tzi.m_bias + (tzi.m_standard_range.inRange(local_time) ? tzi.m_standard_bias : tzi.m_daylight_bias);
}
//--------------------------------------------------------------------------------
int TimeZone::TimeZone::biasSeconds(int utc, int* started_at_utc, int* expired_at_utc) const
{
	Time gmt_time(utc), local_time;

	const TZI& tzi = _tziFor(Time(utc - m_tzi.m_bias * 60)); // May be not accurate within +- one hour from new year

	TIME_ZONE_INFORMATION tzi_param;
	tzi.fill(&tzi_param);

	::SystemTimeToTzSpecificLocalTime(&tzi_param, &gmt_time, &local_time);

	const int bias = utc - local_time.utc(0);

	if (started_at_utc && expired_at_utc)
	{
		Time started_at, expired_at;
		tzi.m_standard_range.hitWithinYear(local_time, &started_at, &expired_at);
		*started_at_utc = started_at.utc(bias);
		*expired_at_utc = expired_at.utc(bias);
	}

	return bias;
}
//--------------------------------------------------------------------------------
void TimeZone::TimeZone::toUniversalTime(Time& local_time, Time* universal) const
{
	const TZI& tzi = _tziFor(local_time);

	TIME_ZONE_INFORMATION tzi_param;
	tzi.fill(&tzi_param);

	::TzSpecificLocalTimeToSystemTime(&tzi_param, &local_time, universal);
}
//--------------------------------------------------------------------------------
bool TimeZone::TimeZone::isStandardTime(const Time& local_time) const
{
	return _tziFor(local_time).m_standard_range.inRange(local_time);
}
